/**
 * AGNWind.java 
 */

package agngui.agnmodel;

import java.util.ArrayList;
import java.util.List;

import javax.media.j3d.Appearance;
import javax.media.j3d.Geometry;
import javax.media.j3d.Material;
import javax.media.j3d.QuadArray;
import javax.media.j3d.Transform3D;
import javax.vecmath.AxisAngle4d;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

/**
 * Manages the geometry of the wind scattering region defined by the space between two cones.
 */
public class AGNWind extends AGNObject {
    /**
     * AGNWind
     * 
     * Creates the wind geometry
     * @param Ri radius of the inner sphere
     * @param Ro radius of the outer sphere
     * @param HAi half angle of the inner surface in degrees
     * @param HAo half angle of the outer surface in degrees  
     * @param n   The number of faces for the cone
     */
    public AGNWind( double Ri, double Ro, double HAi, double HAo, int n, AGNModelGeometry modelGeometry ) {
        super( AGNModelGeometry.SCATTERWIND, modelGeometry );
        
        this.Ri  = Ri;
        this.Ro  = Ro;   
        this.ha  = HAi;
        this.hao = HAo;
        this.n   = n;
        
        // Create and add geometry
        geometry = createGeometry();
        for(Geometry g : geometry) {
        	addGeometry(g);
        }
		
		setTransparency(0.5f);
		Appearance app = getAppearance();
		Material mat = app.getMaterial();
		mat.setDiffuseColor(new Color3f(0,0,1.0f));
		mat.setAmbientColor(new Color3f(0,0,0.4f));
		app.setMaterial(mat);
		setAppearance(app);

        transformGroup.addChild( this );
    }

    /**
     * generatePoints
     *
     * @param RIi  The radius of the circle on the "floor" of the inner cone
     * @param RIo  The radius of the circle on the "cap" of the inner cone
     * @param ROi  The radius of the circle on the "floor" of the outer cone
     * @param ROo  The radius of the circle on the "cap" of the inner cone
     * @param HAi  The half angle of the inner cone in RADIANS
     * @param HAo  The half angle of the outer cone in RADIANS
     * @param n    The number of facets to use
     * @return an array of points defining the cone in no useful order
     */
    private Point3d[] generatePoints( double RIi, double RIo, double ROi, double ROo, 
                                                  double HAi, double HAo, int n ) {
        double dTheta = ( 2 * Math.PI ) / n;
        double xi, zi; // Inner coordinates
        double xo, zo; // Outer coordinates
        
        List<Point3d> verts = new ArrayList<Point3d>();
        List<Vector3f> norms = new ArrayList<Vector3f>();

        // Create initial "square"
        Point3d inner_surface_outer_radius, inner_surface_inner_radius; // Inner surface points
        Point3d outer_surface_outer_radius, outer_surface_inner_radius; // Outer surface points

        // Let's do the inner surface first
        xi = RIo;
        zi = RIo / Math.tan( HAi );
        inner_surface_outer_radius = new Point3d( xi, 0.0, zi );

        xi = RIi;
        zi = RIi / Math.tan( HAi );
        inner_surface_inner_radius = new Point3d( xi, 0.0, zi );

        xo = ROo;
        zo = ROo / Math.tan( HAo );
        outer_surface_outer_radius = new Point3d( xo, 0.0, zo );
        
        xo = ROi;
        zo = ROi / Math.tan( HAo );
        outer_surface_inner_radius = new Point3d( xo, 0.0, zo );

        // Generate the points
        for( double theta = 0; theta < 2*Math.PI; theta += dTheta ) {
            Point3d new_inner_surface_outer_radius, new_inner_surface_inner_radius;
            Point3d new_outer_surface_outer_radius, new_outer_surface_inner_radius;

            AxisAngle4d axisAngle = new AxisAngle4d( new Vector3d( 0.0, 0.0, 1.0 ), theta );
            Transform3D transform = new Transform3D();

            transform.setRotation( axisAngle );

            new_inner_surface_outer_radius = new Point3d( inner_surface_outer_radius );
            new_inner_surface_inner_radius = new Point3d( inner_surface_inner_radius );
            new_outer_surface_outer_radius = new Point3d( outer_surface_outer_radius );
            new_outer_surface_inner_radius = new Point3d( outer_surface_inner_radius );

            transform.transform( new_inner_surface_outer_radius );
            transform.transform( new_inner_surface_inner_radius );
            transform.transform( new_outer_surface_outer_radius );
            transform.transform( new_outer_surface_inner_radius );

            verts.add( new_inner_surface_outer_radius );//0 mod 4
            verts.add( new_inner_surface_inner_radius );//1 mod 4
            verts.add( new_outer_surface_outer_radius );//2 mod 4
            verts.add( new_outer_surface_inner_radius );//3 mod 4
            
            norms.add(getVertexNormal(new_inner_surface_outer_radius, new_inner_surface_inner_radius, new_outer_surface_outer_radius));
            norms.add(getVertexNormal(new_inner_surface_inner_radius, new_inner_surface_outer_radius, new_outer_surface_inner_radius));
            norms.add(getVertexNormal(new_outer_surface_outer_radius, new_outer_surface_inner_radius, new_inner_surface_outer_radius));
            norms.add(getVertexNormal(new_outer_surface_inner_radius, new_outer_surface_outer_radius, new_inner_surface_inner_radius));
        }

        norm_array = norms.toArray(new Vector3f[0]);
        
        return verts.toArray( new Point3d[0] );
    }

    /**
     * createGeometry   
     * 
     * Creates the geometry to render the wind
     * 
     * @return an array of geometry
     */
    protected Geometry[] createGeometry() {
        double HAi = ha * Math.PI / 180.0;
        double HAo = hao * Math.PI / 180.0;
        
        double RIi = Ri * Math.tan( HAi );
        double RIo = Ro * Math.tan( HAi );
        double ROi = Ri * Math.tan( HAo );
        double ROo = Ro * Math.tan( HAo );

        Point3d[] points = generatePoints( RIi, RIo, ROi, ROo, HAi, HAo, n );                                              

        List<Point3d>  point_list = new ArrayList<Point3d>();
        List<Vector3f> norm_list  = new ArrayList<Vector3f>();
        
        Geometry geometryArray[]  = null;

        // Generate the inner shell
        for( int i = 0; i < n; ++i ) {
            int index = 4 * i;
            int max   = n * 4;
            
            int i0 = (index + 1) % max;//mod 1 is inner surface, inner radius
            int i1 = (index) % max;//mod 0 is inner surface, outer radius
            int i2 = (index + 4) % max;
            int i3 = (index + 5) % max;
            // 1 0 4 5
            point_list.add( points[i0] );
            point_list.add( points[i1] );
            point_list.add( points[i2] );
            point_list.add( points[i3] );
            
            norm_list.add( norm_array[i0] );
            norm_list.add( norm_array[i1] );
            norm_list.add( norm_array[i2] );
            norm_list.add( norm_array[i3] );
        }

        // Generate the outer shell
        for( int i = 0; i < n; ++i ) {
            int index = 4 * i;
            int max   = n * 4;

            // 2 3 7 6
            int i0 = (index + 2) % max;//mod 2 is outer surface, outer radius
            int i1 = (index + 3) % max;//mod 3 is outer surface, inner radius
            int i2 = (index + 7) % max;
            int i3 = (index + 6) % max;
            point_list.add( points[i0] );
            point_list.add( points[i1] );
            point_list.add( points[i2] );
            point_list.add( points[i3] );
            
            norm_list.add( norm_array[i0] );
            norm_list.add( norm_array[i1] );
            norm_list.add( norm_array[i2] );
            norm_list.add( norm_array[i3] );
        }

        // Generate the top shell
        for( int i = 0; i < n; ++i ) {
            int index = 4 * i;
            int max   = n * 4;

            // 0 2 6 4
            int i0 = (index) % max;//mod 0 is inner surface, outer radius
            int i1 = (index + 2) % max;//mod 2 is outer surface, outer radius
            int i2 = (index + 6) % max;
            int i3 = (index + 4) % max;
            point_list.add( points[i0] );
            point_list.add( points[i1] );
            point_list.add( points[i2] );
            point_list.add( points[i3] );
            
            norm_list.add( norm_array[i0] );
            norm_list.add( norm_array[i1] );
            norm_list.add( norm_array[i2] );
            norm_list.add( norm_array[i3] );
        }

        // Generate the bottom shell
        for( int i = 0; i < n; ++i ) {
            int index = 4 * i;
            int max   = n * 4;

            // 3 1 5 7
            int i0 = (index + 3) % max;//mod 3 is outer surface, inner radius
            int i1 = (index + 1) % max;//mod 1 is inner surface, inner radius
            int i2 = (index + 5) % max;
            int i3 = (index + 7) % max;
            point_list.add( points[i0] );
            point_list.add( points[i1] );
            point_list.add( points[i2] );
            point_list.add( points[i3] );
            
            norm_list.add( norm_array[i0] );
            norm_list.add( norm_array[i1] );
            norm_list.add( norm_array[i2] );
            norm_list.add( norm_array[i3] );
        }

        point_array = point_list.toArray(new Point3d[0]);
        norm_array = norm_list.toArray(new Vector3f[0]);
        QuadArray quads = new QuadArray(point_list.size(), QuadArray.COORDINATES | QuadArray.NORMALS);
        quads.setCoordinates(0, point_array);
        quads.setNormals(0, norm_array);

        geometryArray = new Geometry[]{ quads };
        
        generateHandles();

        return geometryArray;
    } 

    /**
     * generateHandles
     *
     * Generates the wind HUD handles based on the state of the internal
     * variables.
     */
    public void generateHandles() {
        double   xA, zA;
        Point3d  points[]               = new Point3d[6];
        Vector3d linHandleAxes          = new Vector3d( 0.0, 0.0, -1.0 );
        Vector3d anHandleAxes           = new Vector3d( 0.0, 1.0, 0.0 );
        Vector3d rotHandleAxisPolar     = new Vector3d( 1.0, 0.0, 0.0 );
        Vector3d rotHandleAxisAzimuthal = new Vector3d( 0.0, 0.0, 1.0 );
        AxisAngle4d polarRotation       = new AxisAngle4d();
        Transform3D axisTransform       = new Transform3D();
        Transform3D objectTransform;
        double rotRadius   = Ro / 4.0;
        double rotDistance = Ro * 1.3;
        double recentPolarValue     = (handles == null) ? 0.0 : handles[4].getRecentValue();
        double recentAzimuthalValue = (handles == null) ? 0.0 : handles[5].getRecentValue();
        double HAi = ha  * Math.PI / 180.0;
        double HAo = hao * Math.PI / 180.0;

        if( handles == null ) {
            handles = new AGNParameterHandle[points.length];
        } else {
        	// Transform polar axis according to azimuthal angle
            polarRotation.set( rotHandleAxisAzimuthal, recentAzimuthalValue );
            axisTransform.setRotation( polarRotation );
        }
        
        objectTransform = getFullTransform();

        // Inner radius handle
        points[0] = new Point3d( 0.0, 0.0, Ri );

        // Outer radius handle
        points[1] = new Point3d( 0.0, 0.0, Ro );

        // Inner half angle handle
        xA = Ro * Math.tan( HAi );
        zA = Ro;
        points[2] = new Point3d( xA, 0.0, zA );

        // Outer half angle handle
        xA = Ro * Math.tan( HAo );
        zA = Ro;
        points[2] = new Point3d( xA, 0.0, zA );
        
        // Inner half angle handle
        xA = Ro * Math.tan( HAi );
        zA = Ro;
        points[3] = new Point3d( xA, 0.0, zA );

        // Polar handle
        points[4] = new Point3d( rotDistance, 0.0, rotRadius );
        points[4].scale( SCALE_FACTOR );

        // Azimuthal handle
        points[5] = new Point3d( rotRadius, 0.0, rotDistance );
        points[5].scale( SCALE_FACTOR );

        objectTransform.transform( points[0] );
        objectTransform.transform( points[1] );
        objectTransform.transform( points[2] );
        objectTransform.transform( points[3] );
        axisTransform.transform( points[4] );
        objectTransform.transform( linHandleAxes );
        objectTransform.transform( anHandleAxes  );
        axisTransform.transform( rotHandleAxisPolar );

        handles[0] = new AGNLinearParameterHandle( this, points[0], linHandleAxes, ID_RI  );
        handles[1] = new AGNLinearParameterHandle( this, points[1], linHandleAxes, ID_RO  );
        handles[2] = new AGNAngularParameterHandle( this, points[2], anHandleAxes, ID_HAO );
        handles[3] = new AGNAngularParameterHandle( this, points[3], anHandleAxes, ID_HA );
        handles[4] = new AGNRotationalParameterHandle( this, points[4], rotHandleAxisPolar,
                                                       recentPolarValue, ID_ROT_POLAR );
        handles[5] = new AGNRotationalParameterHandle( this, points[5], rotHandleAxisAzimuthal,
                                                       recentAzimuthalValue, ID_ROT_AZIMUTHAL );
    }

}
